package com.ruoyi.act.config;

import cn.hutool.crypto.digest.MD5;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.act.config.annotation.VerifyIgnore;
import com.ruoyi.act.module.domain.ActBizInfo;
import com.ruoyi.act.module.service.IActBizInfoService;
import com.ruoyi.act.module.service.IJJwtService;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 工作流租户对象 act_biz_info
 *
 * @author hcj
 * @date 2020-12-24
 */
public class ApisInterceptor implements HandlerInterceptor {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApisInterceptor.class);

    public static final String SIGNATURE = "signature";
    public static final String TIME_STAMP = "timestamp";
    public static final String RANDOM_STR = "randomStr";
    public static final String APP_ID = "appid";
    public static final String RE_SUBMIT = "resubmit:uri:";

    private RedisCache redisCache = SpringUtils.getBean("redisCache");
    private IJJwtService ijJwtService = SpringUtils.getBean("ijJwtService");
    private IActBizInfoService iActBizInfoService = SpringUtils.getBean("iActBizInfoService");

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        String body = new RequestWrapper(request).getBody();

        Map<String, Object> map = null;
        if (StringUtils.isBlank(body)) {
            Map<String, String[]> params = request.getParameterMap();
            Map<String, Object> finalMap = map;
            params.entrySet().forEach(info -> {
                finalMap.put(info.getKey(), info.getValue()[0]);
            });
        } else {
            map = strToMap(body);
        }

        if (null == map) {
            errorResponse(response, "请求参数异常");
            return false;
        }

        handlerParam(map);

        if (null == map.get("appid") || StringUtils.isBlank(map.get("appid").toString())) {
            errorResponse(response, "请求参数异常");
            return false;
        }

        if (null == map.get(SIGNATURE) || StringUtils.isBlank(map.get(SIGNATURE).toString())) {
            errorResponse(response, "请求参数异常");
            return false;
        }

        if (null == map.get(TIME_STAMP) || StringUtils.isBlank(map.get(TIME_STAMP).toString())) {
            errorResponse(response, "请求参数异常");
            return false;
        } else {
            //判断是否时间戳 true 表示参数异常
            if (ckIsTimestamp(map.get(TIME_STAMP).toString())) {
                errorResponse(response, "请求参数异常");
                return false;
            }
        }

        if (null == map.get(RANDOM_STR) || StringUtils.isBlank(map.get(RANDOM_STR).toString())) {
            errorResponse(response, "请求参数异常");
            return false;
        }

        //根据appid来获取secretkey
        String secretKey = iActBizInfoService.selectSecretKeyByAppId(map.get(APP_ID).toString());

        if (null == secretKey) {
            errorResponse(response, "请求参数异常");
            return false;
        }

        String sign = MD5.create().digestHex(map.get("signStr").toString() + secretKey);
        if (!map.get(SIGNATURE).equals(sign)) {
            errorResponse(response, "参数异常");
            return false;
        }

        String re_key = RE_SUBMIT + request.getRequestURI();
        boolean res = repeat(re_key, map, sign);
        if (res) {
            errorResponse(response, "重复请求");
            return false;
        }

        VerifyIgnore ignore = ((HandlerMethod) handler).getMethodAnnotation(VerifyIgnore.class);
        if (ignore != null) {
            return true;
        }

        ActBizInfo actBizInfo = ijJwtService.getTenant(request);

        if (null == actBizInfo) {
            errorResponse(response, "请先登录");
            return false;
        }

        ijJwtService.verifyToken(actBizInfo);

        return true;
    }

    private boolean repeat(String key, Map<String, Object> map, String sign) {
        Map<String, Object> param = redisCache.getCacheObject(key);

        if (null != param) {
            long time1 = Long.parseLong(map.get(TIME_STAMP).toString());//当前请求
            long time2 = Long.parseLong(param.get(TIME_STAMP).toString());//上次请求

            long diff = ((time1 - time2)) / 1000;
            String sign1 = param.get(SIGNATURE).toString();

            if (sign1.equals(sign) && diff < 10) {
                return true;
            }

        } else {
            redisCache.setCacheObject(key, map, 10, TimeUnit.SECONDS);
        }
        return false;
    }

    private void errorResponse(HttpServletResponse response, String msg) throws IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.println(JSONObject.toJSON(AjaxResult.error(msg)));
        writer.close();
    }

    private Map<String, Object> handlerParam(Map<String, Object> map) throws UnsupportedEncodingException {

        // 字典排序
        List<Map.Entry<String, Object>> entryList = new ArrayList<>(map.entrySet());
        // 对所有传入参数按照字段名的 ASCII 码从小到大排序（字典序）
        Collections.sort(entryList, Comparator.comparing(Map.Entry::getKey));

        // 构造加密字符串
        StringBuffer sb = new StringBuffer();
        for (Map.Entry<String, Object> entry : entryList) {
            if (entry.getKey().equals("signature")) {
                continue;
            }
            sb.append(entry.getKey() + "=" + entry.getValue().toString());
            sb.append("&");
        }
        map.put("signStr", sb.toString());

        return map;
    }

    /**
     * 检测是否时间戳
     *
     * @param timestamp
     * @return
     */
    private Boolean ckIsTimestamp(String timestamp) {

        if (timestamp.length() < 13) {
            return true;
        }
        try {
            new Date(Long.parseLong(timestamp));
        } catch (Exception e) {
            e.printStackTrace();
            return true;
        }

        return false;
    }


    public static Map<String, Object> strToMap(String str) {
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Object> map = null;
        try {
            map = mapper.readValue(str, Map.class);
        } catch (JsonMappingException e) {
            LOGGER.error("map集合参数转换异常{}", "JsonMappingException");
        } catch (JsonProcessingException e) {
            LOGGER.error("map集合参数转换异常{}", "JsonProcessingException");
        }
        return map;
    }
}